// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jaewon

#pragma once

#include <deque>
#include <optional>
#include <utility>

#include <boost/call_traits.hpp>
#include <glog/logging.h>

namespace math {
namespace detail_mw {

struct NullHandler {
  template <typename P>
  void OnPushed(P&&) {}
  template <typename P>
  void OnPopped(P&&) {}
  void OnClear() {}
};

template <typename Handler>
struct PassHandler {
  PassHandler() : handler(nullptr) {}
  explicit PassHandler(Handler* handler) : handler(handler) {}

  template <typename P>
  void OnPushed(P& value) {
    handler->OnPushed(std::forward<P>(value));
  }

  template <typename P>
  void OnPopped(P&& value) {
    handler->OnPopped(std::forward<P>(value));
  }

  void OnClear() { handler->OnClear(); }

  Handler* handler;
};

template <typename T, typename Handler>
struct LastValueWrapper : public Handler {
  using ValueType = T;
  using ValueParamType = typename boost::call_traits<ValueType>::param_type;

  template <typename... Args>
  explicit LastValueWrapper(Args&&... args) : Handler(std::forward<Args>(args)...) {}

  void OnPushed(ValueParamType value) {
    Handler::OnPushed(value);
    last_pushed = value;
  }

  void OnPopped(ValueParamType value) {
    last_popped = value;
    Handler::OnPopped(value);
  }

  template <typename P>
  void OnPopped(P&& value) {
    last_popped = std::move(value);
    Handler::OnPopped(last_popped.value());
  }

  void OnClear() {
    last_pushed = std::nullopt;
    last_popped = std::nullopt;
    Handler::OnClear();
  }

  std::optional<ValueType> last_pushed;
  std::optional<ValueType> last_popped;
};

}  // namespace detail_mw

template <typename T, typename Handler>
class MovingWindow {
 public:
  using ValueType = T;
  using ValueParamType = typename boost::call_traits<ValueType>::param_type;

  // TODO(jaewon): Remove deque. Make a simple circular queue.
  using Container = std::deque<ValueType>;
  using Iterator = typename Container::iterator;
  using ConstIterator = typename Container::const_iterator;

  template <typename... Args>
  explicit MovingWindow(Args&&... args) : handler_(std::forward<Args>(args)...) {}

  void Push(ValueParamType value) {
    window_.push_back(value);
    handler_.OnPushed(value);
    TryPop();
  }

  template <typename... Args>
  void Emplace(Args&&... args) {
    window_.emplace_back(std::forward<Args>(args)...);
    handler_.OnPushed(window_.back());
    TryPop();
  }

  void ResizeWindow(size_t window_size) {
    DCHECK_GE(window_size, 0);
    window_size_ = window_size;
    if (!empty()) {
      TryPop();
    }
  }

  void Clear() {
    window_.clear();
    handler_.OnClear();
  }

  size_t num_popped() const { return num_popped_; }
  size_t window_size() const { return window_size_; }

  bool empty() const { return window_.empty(); }
  size_t size() const { return window_.size(); }

  const ValueType& front() const { return window_.front(); }
  const ValueType& back() const { return window_.back(); }

  Iterator begin() { return window_.begin(); }
  Iterator end() { return window_.end(); }
  ConstIterator begin() const { return window_.begin(); }
  ConstIterator end() const { return window_.end(); }

  Handler& handler() { return handler_; }
  const Handler& handler() const { return handler_; }

 private:
  inline void TryPop() {
    while (window_.size() > window_size_) {
      ++num_popped_;
      handler_.OnPopped(std::move(window_.front()));
      window_.pop_front();
    }
  }

  Handler handler_;
  size_t window_size_ = 0;
  size_t num_popped_ = 0;
  Container window_;
};

template <typename T, bool LastValueStore = false>
class SimpleMovingWindow;

template <typename T>
class SimpleMovingWindow<T, false> : public MovingWindow<T, detail_mw::NullHandler> {};

template <typename T>
class SimpleMovingWindow<T, true>
    : public MovingWindow<T, detail_mw::LastValueWrapper<T, detail_mw::NullHandler>> {};

template <typename T, typename Handler>
class LastValueStoreMovingWindow : public MovingWindow<T, detail_mw::LastValueWrapper<T, Handler>> {
};

}  // namespace math
